/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.view.elements;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import org.jfonia.connect5.basics.Observer;
import org.jfonia.connect5.intervals.BasicDoubleInterval;
import org.jfonia.connect5.intervals.Interval;
import org.jfonia.connect5.intervals.LinkedDoubleIntervalList;
import org.jfonia.connect5.numerical.UnidirectionalDoubleSumRelation;
import org.jfonia.connect5.relations.ABRelation;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.constants.ModelConstants;
import org.jfonia.constants.SymbolConstants;
import org.jfonia.constants.ViewConstants;
import org.jfonia.model.ISequence;
import org.jfonia.model.Sequence;
import org.jfonia.model.elements.Beat;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.model.elements.Clef;
import org.jfonia.model.elements.Clef.Type;
import org.jfonia.model.elements.IStaffElement;
import org.jfonia.model.elements.KeySignature;
import org.jfonia.model.elements.TimeSignature;
import org.jfonia.model.elements.Tone;
import org.jfonia.pitch.Base40;
import org.jfonia.pitch.MusicalInterval;
import org.jfonia.view.SequenceView;
import org.jfonia.notation.Bar;
import org.jfonia.notation.Notation;
import org.jfonia.notation.ObservableNote;
import org.jfonia.notation.ObservableRest;
import org.jfonia.notation.ObservableNoteOrRest;
import org.jfonia.view.relations.DoubleInversionRelation;
import org.jfonia.view.symbols.BarSymbol;
import org.jfonia.view.symbols.ClefSymbol;
import org.jfonia.view.symbols.IRankedStaffSymbol;
import org.jfonia.view.symbols.IStaffSymbol;
import org.jfonia.view.symbols.ISymbol;
import org.jfonia.view.symbols.KeySignatureSymbol;
import org.jfonia.view.symbols.TimeSignatureSymbol;
import org.jfonia.view.symbols.NoteSymbol;
import org.jfonia.view.symbols.RestSymbol;
import org.jfonia.view.symbols.TieSymbol;
import percussiongenerator.model.Pair;

/**
 *
 * @author Rik Bauwens
 */
public class StaffPart extends PartPanel implements IElement, KeyListener, Observer
{
        //Observers
        private Set<Observer> observers = new LinkedHashSet<Observer>();

        private SequenceView sequenceView;

        private MutableValueNode<Double> rootStaffBase;
        private MutableValueNode<Double> rootPostStaffBase;

        private Map<IStaffElement, BasicDoubleInterval> staffElements; //temporary,
        //will be connected to a symbol (relation)
        private LinkedDoubleIntervalList xNodes;
        private MutableValueNode<Double> initialWidthNode;
        private MutableValueNode<Double> preferredWidthNode;

        private List<IRankedStaffSymbol> staffSymbols;
        private Caret caret;
        private IRankedStaffSymbol selectedStaffSymbol;

        //private Map<BasicDoubleInterval, IStaffSymbol> intervalToSymbolMapper;

        //Nodes to store the begin and end musicaltime of this staffpart
        //Values are updated automatically with min/max relations
        private MutableValueNode<Integer> beginNode;
        private Equal<Integer> beginRelation;
        private MutableValueNode<Integer> endNode;
        private Equal<Integer> endRelation;
        private MutableValueNode<Integer> centralCRankNode;
        private Equal<Integer> centralCRankRelation;

        private boolean rendered;

        private int selectionDirection;

        public StaffPart(ValueNode<Double> frameX, ValueNode<Double> staffY,
                         ValueNode<Double> frameWidth, MutableValueNode<Double> staffHeight,
                         MutableValueNode<Double> rootStaffBase, MutableValueNode<Double> rootPostStaffBase,
                         SequenceView sequenceView)
        {
                super(frameX, staffY, frameWidth, staffHeight);
                this.sequenceView = sequenceView;

                //RootStaffBase is the staffbase shared by all staffParts of one staff
                //(equal to the maximum of staffBases (of the same staff)
                this.rootStaffBase = rootStaffBase;
                //Space beneath the staffbase
                this.rootPostStaffBase = rootPostStaffBase;

                //Linked list of intervals, which represent the width of the symbols.
                xNodes = new LinkedDoubleIntervalList();

                //Set the initial width of this staffpart (to make sure it's visible
                //even when there are no elements added)
                initialWidthNode = new BasicValueNode<Double>(ViewConstants.INITIAL_FRAME_WIDTH);

                //The preferred width is the sum of the widths of all the symbols
                //added to this staffpart
                this.preferredWidthNode = new BasicValueNode<Double>(0.0);
                MutableValueNode<Double> invertedNode = new BasicValueNode<Double>(0.0);
                new DoubleInversionRelation(xNodes.getBeginNode(), invertedNode);
                MutableValueNode<Double> xNodesWidthNode = new BasicValueNode<Double>(0.0);
                new UnidirectionalDoubleSumRelation(xNodes.getEndNode(), invertedNode, xNodesWidthNode);
                MutableValueNode<Double> notRoundedWidthNode = new BasicValueNode<Double>(0.0);
                new UnidirectionalDoubleSumRelation(xNodesWidthNode, initialWidthNode, notRoundedWidthNode);
                //The panels' dimensions are measured in int values, so make sure
                //that the preferred width is greater than or equal to the width
                //of its components. When this value is in between two int values,
                //opt for the bigger one to ensure the dimensions are sufficient
                new RoundRelation(notRoundedWidthNode, preferredWidthNode);

                //Create a caret (vertical line placed between two symbols)
                //A caret is represented as and extra interval in the xNodes
                //list, between two other intervals (corresponding with the symbols)
                caret = new Caret(this, xNodes, staffHeight);

                //Arraylist of staffelements that need to be represented
                //by one (or more) staffsymbols
                staffElements = new HashMap<IStaffElement, BasicDoubleInterval>();
                //Staffsymbols to be painted on this staffpart
                staffSymbols = new LinkedList<IRankedStaffSymbol>();

                //Mapper used to get the symbol corresponding with an interval
                //intervalToSymbolMapper = new HashMap<BasicDoubleInterval, IStaffSymbol>();
                //Musicaltime of this element
                beginNode = new BasicValueNode<Integer>(0);
                beginNode.addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                if(isValidMusicalDuration())
                                        notifyObservers();
                        }
                });
                endNode = new BasicValueNode<Integer>(0);
                endNode.addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                if(isValidMusicalDuration())
                                        notifyObservers();
                        }
                });
                centralCRankNode = new BasicValueNode<Integer>(0);

                //This staffpart is not yet rendered
                rendered = false;
                setBackground(ViewConstants.BACKGROUND_COLOR);
                //To ensure that this panel is painted
                setPreferredSize(new Dimension(1, 1));
                addKeyListener(this);

                //setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
                init();
        }

        private boolean isValidMusicalDuration()
        {
                ObservableNoteOrRest noteOrRest = getNoteOrRest();
                if(noteOrRest == null)
                        return true;
                return (getEnd() - getBegin()) == noteOrRest.getDifference();
        }

        private void init()
        {
                rootStaffBase.addObserver( new Observer() {
			public void onNotify(Object source)
			{
				heightNode.setValue(Math.ceil(rootStaffBase.getValue() + rootPostStaffBase.getValue()));
			}
		});
                rootPostStaffBase.addObserver( new Observer() {
			public void onNotify(Object source)
			{
				heightNode.setValue(Math.ceil(rootStaffBase.getValue() + rootPostStaffBase.getValue()));
			}
		});
                xNodes.getEndNode().addObserver(new Observer() {
			public void onNotify(Object source)
			{
                                if(staffElements.isEmpty() && initialWidthNode.getValue().equals(new Double(0.0)))
                                        //Add the initial width interval when no other elements are added
                                        initialWidthNode.setValue(ViewConstants.INITIAL_FRAME_WIDTH);
                                else if(!staffElements.isEmpty() && !initialWidthNode.getValue().equals(new Double(0)))
                                        //Remove the initial width interval when other elements are added
                                        initialWidthNode.setValue(0.0);
                                //widthNode.setValue(xNodes.getDifference());
			}
		});
        }

        //TODO: temporary (debug)
        public Set<IStaffElement> getStaffElements()
        {
                return staffElements.keySet();
        }

        public int getBegin()
        {
                return getBeginNode().getValue();
        }

        public int getEnd()
        {
                return getEndNode().getValue();
        }

        public ValueNode<Double> getPreferredWidthNode()
        {
                return preferredWidthNode;
        }

        public ValueNode<Integer> getBeginNode()
        {
                return beginNode;
        }

        public ValueNode<Integer> getEndNode()
        {
                return endNode;
        }

        public int getSymbolCount()
        {
                return xNodes.size();
        }

        public synchronized BasicDoubleInterval getInterval(IStaffElement staffElement)
        {
                if(!staffElements.containsKey(staffElement) ||
                        staffElements.get(staffElement) == null)
                {
                        //Ensure that the symbols are drawn in the correct order,
                        //using the priority map found in SymbolConstants
                        int index = -1;
                        int priority = SymbolConstants.staffSymbolPriority.get(staffElement.getClass());
                        int minPriority = Integer.MAX_VALUE;
                        for(IStaffElement otherStaffElement : staffElements.keySet())
                        {
                                if(otherStaffElement != staffElement)
                                {
                                        int otherPriority = SymbolConstants.staffSymbolPriority.get(otherStaffElement.getClass());
                                        BasicDoubleInterval otherInterval = staffElements.get(otherStaffElement);
                                        if(priority < otherPriority && otherPriority < minPriority && otherInterval != null)
                                        {
                                                index = xNodes.indexOf(otherInterval);
                                                minPriority = otherPriority;
                                        }
                                }
                        }
                        
                        BasicDoubleInterval interval = new BasicDoubleInterval();
                        staffElements.put(staffElement, interval);
                        if(index < 0)
                                xNodes.add(interval);
                        else
                                xNodes.insert(index, interval);
                        return interval;
                }
                return staffElements.get(staffElement);
        }

        public void removeInterval(IStaffElement staffElement)
        {
                if(staffElements.containsKey(staffElement))
                {
                        //Remove interval from linked list
                        if(staffElements.get(staffElement) != null)
                                xNodes.remove(xNodes.indexOf(staffElements.get(staffElement)));
                        //Remove staffelement and interval from map
                        staffElements.remove(staffElement);
                }
        }

        public List<Pair<ISequence, MusicalElement>> getMusicalElements()
        {
                List<Pair<ISequence, MusicalElement>> musicalElements
                        = new ArrayList<Pair<ISequence, MusicalElement>>();
                for(IStaffElement staffElement : staffElements.keySet())
                {
                        if(staffElement instanceof Clef)
                                musicalElements.add(new Pair<ISequence, MusicalElement>(
                                sequenceView.getClefSequence(), (MusicalElement) staffElement));
                        else if(staffElement instanceof KeySignature)
                                musicalElements.add(new Pair<ISequence, MusicalElement>(
                                sequenceView.getKeySignatureSequence(), (MusicalElement) staffElement));
                        else if(staffElement instanceof TimeSignature)
                                musicalElements.add(new Pair<ISequence, MusicalElement>(
                                sequenceView.getTimeSignatureSequence(), (MusicalElement) staffElement));
                        else
                                musicalElements.add(new Pair<ISequence, MusicalElement>(
                                sequenceView.getStaffElementSequence(), (MusicalElement) staffElement));
                }
                return musicalElements;
        }

        public int getTickPosition()
        {
                if(caret.isSet() && caret.getIndex() < xNodes.size())
                {
                        Interval interval = xNodes.getIntervalFromIndex(caret.getIndex());
                        for(IStaffElement staffElement : staffElements.keySet())
                        {
                                if(staffElements.get(staffElement) == interval)
                                        return staffElement.getBegin();
                        }
                }
                return getEnd();
        }

        public void addStaffElement(IStaffElement staffElement)
        {
                if(staffElement == null)
                        return;
                if(!staffElements.containsKey(staffElement) &&
                        (staffElements.isEmpty() || !isValidMusicalTime(staffElement)))
                {
                        //Update begin musicaltime of this staffpart
                        if(staffElements.isEmpty())
                        {
                                if(beginRelation != null)
                                        beginRelation.switchOff();
                                beginNode.setValue(staffElement.getBegin());
                                beginRelation = new Equal<Integer>(staffElement.getBeginNode(), beginNode);
                                //Only the end of a note is accounted
                                if(isValidMusicalTime(staffElement))
                                {
                                        if(endRelation != null)
                                                endRelation.switchOff();
                                        endRelation = new Equal<Integer>(staffElement.getEndNode(), endNode);
                                }
                                else
                                {
                                        if(endRelation != null)
                                                endRelation.switchOff();
                                        endRelation = new Equal<Integer>(staffElement.getBeginNode(), endNode);
                                }
                        }
                        staffElements.put(staffElement, null);
                        if(staffElement instanceof Clef)
                        {
                                if(centralCRankRelation != null)
                                        centralCRankRelation.switchOff();
                                centralCRankRelation = new Equal<Integer>(((Clef) staffElement).getCentralCRankNode(), centralCRankNode);
                        }
                        //Rerender symbols
                        rendered = false;
                        repaint();
                        Notation.getInstance().setSelectedElement(this);
                }
        }

        public void removeStaffElement(IStaffElement staffElement)
        {
                if(staffElements.containsKey(staffElement))
                {
                        //Set caret to the previous part
                        //shiftCaret(-2);
                        //Remove interval for this staffelement
                        removeInterval(staffElement);
                        //Remove staffelement
                        staffElements.remove(staffElement);
                        
                        if(staffElements.isEmpty())
                        {
                                beginRelation.switchOff();
                                endRelation.switchOff();
                                beginNode.setValue(-1);
                        }

                        repaint();
                }
        }

        public Clef getClef()
        {
                Sequence sequence = sequenceView.getClefSequence();
                if(sequence == null)
                        return null;
                if(sequence.size() == 0)
                        return null;
                return (Clef) sequence.getMusicalElement(getBegin());
        }

        public KeySignature getKeySignature()
        {
                Sequence sequence = sequenceView.getKeySignatureSequence();
                if(sequence == null)
                        return null;
                if(sequence.size() == 0)
                        return null;
                return (KeySignature) sequence.getMusicalElement(getBegin());
        }

        public TimeSignature getTimeSignature()
        {
                Sequence sequence = sequenceView.getTimeSignatureSequence();
                if(sequence == null)
                        return null;
                if(sequence.size() == 0)
                        return null;
                return (TimeSignature) sequence.getMusicalElement(getBegin());
        }

        public ObservableNoteOrRest getNoteOrRest()
        {
                for(IStaffElement staffElement : staffElements.keySet())
                {
                        if(staffElement instanceof ObservableNoteOrRest)
                                return (ObservableNoteOrRest) staffElement;
                }
                return null;
        }

        public NoteSymbol getNoteSymbol()
        {
                for(IRankedStaffSymbol staffSymbol : staffSymbols)
                {
                        if(staffSymbol instanceof NoteSymbol)
                                return (NoteSymbol) staffSymbol;
                }
                return null;
        }

        public boolean contains(MusicalElement musicalElement)
        {
                for(IStaffElement staffElement : staffElements.keySet())
                {
                        if(staffElement == musicalElement)
                                return true;
                }
                return false;
        }

        public boolean isEmpty()
        {
                return staffElements.isEmpty();
        }

        public static boolean isValidMusicalTime(IStaffElement staffElement)
        {
                //Not valid for Clef, Bar, Key Signature and Time Signature,
                //because they have a musical time that does not
                //correspond with their actual width
                return staffElement instanceof ObservableNoteOrRest;
        }

        private int getTickOffset(int index)
        {
                IStaffElement staffElement = getStaffElement(index);
                if(staffElement != null)
                {
                        if(index == xNodes.size() && isValidMusicalTime(staffElement))
                                return staffElement.getEnd();
                        else
                                return staffElement.getBegin();
                }
                return -1;
        }

        private IStaffElement getStaffElement(int index)
        {
                BasicDoubleInterval interval = null;
                if(index == xNodes.size())
                        interval = (BasicDoubleInterval) xNodes.getIntervalFromIndex(index - 1);
                else
                        interval = (BasicDoubleInterval) xNodes.getIntervalFromIndex(index);

                for(IStaffElement staffElement : staffElements.keySet())
                {
                        if(staffElements.get(staffElement) == interval)
                        {
                                return staffElement;
                        }
                }
                return null;
        }

        private void checkTies(ObservableNoteOrRest noteOrRest, ISymbol symbol)
        {
                for(TieSymbol tieSymbol : sequenceView.getTieSymbolsOnLeft(noteOrRest.getBegin()))
                        tieSymbol.setLeftSymbol(symbol);
                for(TieSymbol tieSymbol : sequenceView.getTieSymbolsOnRight(noteOrRest.getBegin()))
                        tieSymbol.setRightSymbol(symbol);
        }

        @Override
        public void paintComponent(Graphics g)
        {
                super.paintComponent(g);
                if(!rendered)
                        renderComponent(g);
                //Draw the staff lines
                g.setColor(ViewConstants.STAFF_COLOR);
                for(int i = 0; i < ViewConstants.STAFF_LINE_COUNT; i++)
                        g.drawLine(0, (int) Math.ceil(rootStaffBase.getValue()
                                   - i * ViewConstants.STAFF_LINE_SPACING),
                                   getWidth(), (int) Math.ceil(rootStaffBase.getValue()
                                   - i * ViewConstants.STAFF_LINE_SPACING));
                //Draw the symbols
                for(IRankedStaffSymbol symbol : staffSymbols)
                        symbol.paintSymbol((Graphics2D) g);
                //Set the part size
                setPreferredSize(new Dimension(widthNode.getValue().intValue(),
                                               heightNode.getValue().intValue()));
                
                //Caret cannot be correctly drawn before the width of the elements is known
                if(caret.isSet())
                {
                        if(!caret.isValid())
                                caret.set(xNodes.getEnd());
                        requestFocus();
                }
                //Draw the caret
                caret.paint((Graphics2D) g);
        }

        public void renderComponent(Graphics g)
        {
                //Rerender all symbols, so clear the previous list of symbols
                for(IStaffSymbol staffSymbol : staffSymbols)
                        staffSymbol.removeRepaintObserver(this);
                staffSymbols.clear();
                //Iterate over all elements
                for(IStaffElement staffElement : staffElements.keySet())
                {
                        IRankedStaffSymbol symbol = null;
                        //Add an interval to the xNodes list, to ensure that
                        //the added symbol is drawn next to the previous element
                        BasicDoubleInterval xInterval = getInterval(staffElement);
                        //Check the type of the current element
                        if(staffElement instanceof ObservableNoteOrRest)
                        {
                                ObservableNoteOrRest noteOrRest = (ObservableNoteOrRest) staffElement;
                                //Create a symbol
                                if(noteOrRest instanceof ObservableRest)
                                        symbol = new RestSymbol(xNode, yNode, rootStaffBase,
                                                    rootPostStaffBase, xInterval.getBeginNode(),
                                                    xInterval.getDifferenceNode(),
                                                    ((ObservableRest) noteOrRest).getRestNode());
                                else
                                        symbol = new NoteSymbol(xNode, yNode,
                                                rootStaffBase, rootPostStaffBase,
                                                xInterval.getBeginNode(),
                                                xInterval.getDifferenceNode(),
                                                (ObservableNote) noteOrRest);
                                checkTies(noteOrRest, symbol);
                        }
                        else if(staffElement instanceof Clef)
                        {
                                Clef clef = (Clef) staffElement;
                                //Create a symbol
                                symbol = new ClefSymbol(xNode, yNode,
                                        rootStaffBase, rootPostStaffBase,
                                        xInterval.getBeginNode(),
                                        xInterval.getDifferenceNode(),
                                        clef);
                        }
                        else if(staffElement instanceof KeySignature)
                        {
                                KeySignature keySignature = (KeySignature) staffElement;
                                //Create a symbol
                                symbol = new KeySignatureSymbol(xNode, yNode,
                                         rootStaffBase, rootPostStaffBase,
                                         xInterval.getBeginNode(),
                                         xInterval.getDifferenceNode(),
                                         keySignature, centralCRankNode);
                        }
                        else if(staffElement instanceof TimeSignature)
                        {
                                TimeSignature timeSignature = (TimeSignature) staffElement;
                                //Create a symbol
                                symbol = new TimeSignatureSymbol(xNode, yNode,
                                         rootStaffBase, rootPostStaffBase,
                                         xInterval.getBeginNode(),
                                         xInterval.getDifferenceNode(),
                                         timeSignature);
                        }
                        else if(staffElement instanceof Bar)
                        {
                                Bar bar = (Bar) staffElement;
                                //Create a symbol
                                symbol = new BarSymbol(xNode, yNode,
                                         rootStaffBase, rootPostStaffBase,
                                         xInterval.getBeginNode(),
                                         xInterval.getDifferenceNode(),
                                         bar);
                        }
                        if(symbol != null)
                        {
                                //Add it to the list
                                staffSymbols.add(symbol);
                                //Add this as observer
                                symbol.addRepaintObserver(this);
                        }
                }
                //This component is rendered
                rendered = true;
        }

        //DEPRECATED!!!! (all of it, see -> renderComponent() for updates)
        /*private void addStaffElement(int index, IStaffElement staffElement)
        {
                //Update begin musicaltime of this staffpart
                //Update end musicaltime of this staffpart
                if(isValidMusicalTime(staffElement))
                        //Only the end of a note is accounted
                        endMusicalTimeRelations.put(staffElement,
                                new IntMaxRelation(endNode, staffElement.getEndNode()));
                else
                        endMusicalTimeRelations.put(staffElement,
                                new IntMaxRelation(endNode, new BasicValueNode<Integer>(
                                ((Integer) staffElement.getBegin()).intValue() + 1)));

                Rectangle2D r = new Rectangle2D.Double(0, 0,
                                                       0, rootStaffBase.getValue());
                //Add an interval to the xNodes list, to ensure that
                //the added symbol is drawn next to the previous element
                IRankedStaffSymbol symbol = null;
                //Check the type of the current element
                if(staffElement instanceof Tone)
                {
                        Tone tone = (Tone) staffElement;
                        //Create a symbol
                        //symbol = new ToneSymbol(xNode, yNode,
                        //        rootStaffBase, postStaffBase, xInterval.getBeginNode(),
                        //        ((BasicDoubleInterval) xInterval).getDifferenceNode(),
                        //        tone);
                        *//*ToneView toneView = new ToneView(xNode, yNode,
                                rootStaffBase, rootPostStaffBase, xNodes,
                                tone);*//*
                        //Add it to the list
                        //staffSymbols.addAll(toneView.getSymbols());
                }
                else if(staffElement instanceof Clef)
                {
                        Clef clef = (Clef) staffElement;
                        //Add an interval to the xNodes list, to ensure that
                        //the added symbol is drawn next to the previous element
                        BasicDoubleInterval xInterval = new BasicDoubleInterval();
                        xNodes.add(xInterval);
                        //Create a symbol
                        symbol = new ClefSymbol(xNode, yNode,
                                rootStaffBase, rootPostStaffBase, xInterval.getBeginNode(),
                                xInterval.getDifferenceNode(),
                                clef);
                }
                else if(staffElement instanceof TimeSignature)
                {
                        TimeSignature timeSignature = (TimeSignature) staffElement;
                        //Add an interval to the xNodes list, to ensure that
                        //the added symbol is drawn next to the previous element
                        BasicDoubleInterval xInterval = new BasicDoubleInterval();
                        xNodes.add(xInterval);
                        //Create a symbol
                        symbol = new TimeSignatureSymbol(xNode, yNode,
                                 rootStaffBase, rootPostStaffBase, xInterval.getBeginNode(),
                                 xInterval.getDifferenceNode(),
                                 timeSignature);
                }
                //Not for tonesymbols! (symbol is null)
                if(symbol != null)
                {
                        //Add it to the list
                        staffSymbols.add(symbol);
                        //Add its bounds to the boundbox of this part
                        r.add(symbol.getBounds());
                }
                //Store the space beneath the staffbase, to calculate the preferredheight
                //of this staffpart; determine the maximum of the current and new value,
                //and use that maximum as the new value
                //postStaffBase.setValue(Math.max(postStaffBase.getValue(), Math.abs(r.getMaxY() - staffBase.getValue())));
                //Store the width of this part, to use it as prefferedwidth later
                //partWidth = r.getMaxX() - r.getMinX();
                //Set the new staffbase
                //staffBase.setValue(rootStaffBase.getValue() + Math.abs(r.getMinY()));
        }

        //DEPRECATED
        private void removeStaffElement(int index)
        {
                //If this method is called by a next staffpart, this staffpart
                //has no caret yet, so set it to the end of the frame
                if(!hasFocus())
                {
                    double offset = 0;
                    if(xNodes.size() > 0)
                            offset = (Double) xNodes.getIntervalFromIndex(xNodes.size() - 1).getEnd() - xNode.getValue();
                    caret.set(offset);
                }

                if(index < 0)
                {
                        //If this staffPart is empty, check if there is a previous one
                        //and delete the last element from that staffpart (if it exists)
                        StaffPart staffPart = (StaffPart) sequenceView.previousElement(this);
                        if(staffPart != null)
                                staffPart.removeStaffElement(staffPart.getSymbolCount() - 1);
                }
                else
                {
                        double offset = (Double) xNodes.getIntervalFromIndex(index).getBegin()
                                               - xNode.getValue();
                        IRankedStaffSymbol symbol = getSymbol(offset);
                        if(symbol != null)
                        {
                                staffSymbols.remove(symbol);
                                xNodes.remove(index);
                        }
                        requestFocus();
                }
        }

        //DEPRECATED
        public Clef getClef(double offset)
        {
                ClefSymbol clefSymbol = null;
                for(IRankedStaffSymbol symbol : staffSymbols)
                {
                        //Symbol has to be a clef, on the left of the given offset
                        if(symbol instanceof ClefSymbol && offset - symbol.getX() > 0)
                        {
                                if(clefSymbol == null || offset - symbol.getX() < offset - clefSymbol.getX())
                                        clefSymbol = (ClefSymbol) symbol;
                        }
                }
                if(clefSymbol != null)
                        return clefSymbol.getClef();

                //If this staffpart has no clef, use the clef of the previous staffpart
                //(from the same staff)
                StaffPart staffPart = (StaffPart) sequenceView.previousElement(this);
                if(staffPart != null)
                        return staffPart.getClef(staffPart.getX() + staffPart.getWidth());
                
                //When there is no previous frame for this staff, no clef can be found
                return null;
        }*/

        private int getSymbolIndex(double offset)
        {
                int i = 0;
                while(i < staffSymbols.size() && offset - staffSymbols.get(i).getX() != 0)
                        i++;

                if(i < staffSymbols.size())
                    return i;
                return 0;
        }

        //Get the symbol to the left of the given offset
        private IRankedStaffSymbol getSymbol(double offset)
        {
                int index = getSymbolIndex(offset);
                if(index >= 0)
                        return staffSymbols.get(index);
                return null;
        }

        @Override
        public void setSelected(boolean selected)
        {
                super.setSelected(selected);
                //for(IRankedStaffSymbol symbol : staffSymbols)
                //        symbol.setSelected(selected);
                if(selected)
                {
                        if(!caret.isSet())
                                caret.set(xNodes.getEnd());
                        requestFocus();
                }
                else
                        caret.remove();
                repaint();
        }
        
        public void caretPrevious(boolean keepSelected)
        {
                shiftCaret(-1, keepSelected);
        }

        public void caretNext(boolean keepSelected)
        {
                shiftCaret(1, keepSelected);
        }

        public synchronized void shiftCaret(int posCount, boolean keepSelected)
        {
                if(Notation.getInstance().getSelectedElementCount() <= 1
                   || !Notation.getInstance().containsSelectedElement(this))
                {
                        selectionDirection = posCount;
                        Notation.getInstance().addSelectedElement(this);
                }
                boolean remove = (selectionDirection > 0 && posCount < 0
                               || selectionDirection < 0 && posCount > 0);
                //If this method is called by a previous or next staffpart, this staffpart
                //has no caret yet, so set it to the beginning or end of the frame
                if(!hasFocus())
                {
                        if(posCount < 0)
                        {
                                caret.set(0);
                                posCount += 2;
                        }
                        else
                        {
                                caret.set(xNodes.getEnd());
                                posCount--;
                        }
                }

                //Move caret to a frame on the left
                if(caret.getIndex() + posCount < 0)
                {
                        IElement element = sequenceView.previousElement(this);
                        if(element != null)
                        {
                                if(!keepSelected)
                                        Notation.getInstance().setSelectedElement(element);
                                if(remove)
                                        Notation.getInstance().removeSelectedElement(this);
                                caret.remove();
                                element.shiftCaret(posCount + caret.getIndex() - 1, 
                                                   keepSelected);
                        }
                }
                //Move caret to a frame on the right
                else if(caret.getIndex() + posCount > xNodes.size())
                {
                        IElement element = sequenceView.nextElement(this);
                        if(element != null)
                        {
                                if(!keepSelected)
                                        Notation.getInstance().setSelectedElement(element);
                                if(remove)
                                        Notation.getInstance().removeSelectedElement(this);
                                caret.remove();
                                element.shiftCaret(posCount - caret.getIndex() + 1, 
                                                   keepSelected);
                        }
                }
                else
                {
                        caret.shift(posCount);
                }
        }
        
        public void symbolUp(IRankedStaffSymbol staffSymbol)
        {
                if(staffSymbol != null)
                        staffSymbol.incrementRank();
        }
        
        public void symbolDown(IRankedStaffSymbol staffSymbol)
        {
                if(staffSymbol != null)
                        staffSymbol.decrementRank();
        }

        //Select the symbol posCount positions further
        public void shiftSelectedSymbol(int posCount)
        {
                int index = 0;
                if(selectedStaffSymbol != null)
                        index = getSymbolIndex(selectedStaffSymbol.getX());
                //If this method is called by a previous or next staffpart, this staffpart
                //has no caret yet, so set it to the beginning of the frame
                if(!hasFocus())
                {
                        if(posCount < 0)
                        {
                                index = staffSymbols.size() - 1;
                                posCount ++;
                        }
                        else
                        {
                                index = 0;
                        }
                }

                //Move the selection to a frame on the left
                if(index + posCount < 0)
                {
                        StaffPart staffPart = (StaffPart) sequenceView.previousElement(this);
                        if(staffPart != null)
                                staffPart.shiftSelectedSymbol(posCount - index);
                }
                //Move the selection to a frame on the right
                else if(index + posCount >= xNodes.size())
                {
                        StaffPart staffPart = (StaffPart) sequenceView.nextElement(this);
                        if(staffPart != null)
                                staffPart.shiftSelectedSymbol(posCount - (staffSymbols.size() - index));
                }
                else
                {
                        if(selectedStaffSymbol != null)
                                selectedStaffSymbol.setSelected(false);
                        selectedStaffSymbol = staffSymbols.get(index + posCount);
                        selectedStaffSymbol.setSelected(true);
                        requestFocus();
                        //repaint();
                }
        }

        //Select the previous symbol
        public void previousSelectedSymbol()
        {
                shiftSelectedSymbol(-1);
        }

        //Select the next symbol
        public void nextSelectedSymbol()
        {
                shiftSelectedSymbol(1);
        }

        public void selectPreviousPart()
        {
                IElement element = sequenceView.previousElement(this);
                if(element != null)
                        Notation.getInstance().setSelectedElement(element);
        }

        public void selectNextPart()
        {
                IElement element = sequenceView.nextElement(this);
                if(element != null)
                        Notation.getInstance().setSelectedElement(element);
        }

        public void selectPartAbove()
        {
                IElement element = sequenceView.elementAbove(this);
                if(element != null)
                        Notation.getInstance().setSelectedElement(element);
        }

        public void selectPartBelow()
        {
                IElement element = sequenceView.elementBelow(this);
                if(element != null)
                        Notation.getInstance().setSelectedElement(element);
        }

        private int getClosestRank(int givenRank, int rank)
        {
                int octave = givenRank / 7;
                int minRank = (octave - 1) * 7 + rank;
                int maxRank = (octave) * 7 + rank;
                return Math.abs(minRank - givenRank) < Math.abs(maxRank - givenRank) ? minRank : maxRank;
        }

        @Override
        public void mouseClicked(MouseEvent e)
        {
                super.mouseClicked(e);
                if(e.getClickCount() == 1)
                {
                        Notation.getInstance().setSelectedElement(this);
                        /*
                        //Small area around the mouse cursor
                        Rectangle2D rect = new Rectangle2D.Double(e.getX(), e.getY(),
                                ViewConstants.MOUSE_POINTER_REGION_WIDTH,
                                ViewConstants.MOUSE_POINTER_REGION_HEIGHT);

                        //When a symbol was selected, deselect it first,
                        //and set the selected symbol to null in order to
                        //find out if there is a clicked on a symbol this time
                        if(selectedStaffSymbol != null)
                                selectedStaffSymbol.setSelected(false);
                        selectedStaffSymbol = null;

                        //Iterate over all symbols to search for a symbol
                        //where there has been clicked on
                        for(IRankedStaffSymbol symbol : staffSymbols)
                        {
                                if(symbol.intersectsTo(rect))
                                        selectedStaffSymbol = symbol;
                        }

                        //If there was not symbol to be found, check if the caret
                        //is currently set, and set the (new) position
                        if(selectedStaffSymbol == null)
                        {
                                Notation.getInstance().setNote(null);*/
                                //if(caret.isSet())
                                        caret.set(e.getX());
                        /*}
                        //Remove the caret if a symbol is found, and select it
                        else
                        {
                                caret.remove();
                                setAllSequenceSymbolsSelected(false);
                                selectedStaffSymbol.setSelected(true);
                                if(selectedStaffSymbol instanceof NoteSymbol)
                                        Notation.getInstance().setNote(((NoteSymbol)
                                                selectedStaffSymbol).getNote());
                        }
                }
                else if(e.getClickCount() == 2)
                {
                        setAllSequenceSymbolsSelected(false);*/
                        //caret.set(e.getX());
                }
                //repaint();
        }

        public void handleKeyEvent(KeyEvent e)
        {
                if(e.isControlDown())
                        return;
                int tick = -1;
                int rank = 0;
                IStaffElement staffElement = null;
                if(caret.getIndex() >= 0)
                {
                        //Determine tick
                        tick = getTickOffset(caret.getIndex());

                        //Determine rank
                        staffElement = getStaffElement(caret.getIndex());
                        if(staffElement instanceof ObservableNoteOrRest && staffElement instanceof ObservableNote)
                                rank = ((ObservableNote) staffElement).getRank();
                }

                switch(e.getKeyCode())
                {
                        case ViewConstants.INSERT_A_KEYCODE: sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.A_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_B_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.B_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_C_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.C_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_D_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.D_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_E_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.E_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_F_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.F_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_G_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getNoteMusicalTime()).setBase40Rank(
                                Base40.toBase40(new MusicalInterval(getClosestRank(rank,
                                ModelConstants.G_BASE_RANK), Notation.getInstance().getAccidental()))));
                        break;
                        case ViewConstants.INSERT_REST_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Tone(Notation.getInstance().getRestMusicalTime()));
                        break;
                        case ViewConstants.INSERT_CLEF_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Clef().setType(Type.toType(Notation.getInstance().getClef())));
                        break;
                        case ViewConstants.INSERT_KEYSIGNATURE_KEYCODE : sequenceView.addMusicalElement(
                                tick, new KeySignature().setType(Notation.getInstance().getKeySignature()));
                        break;
                        case ViewConstants.INSERT_TIMESIGNATURE_KEYCODE : sequenceView.addMusicalElement(
                                tick, new TimeSignature().setNominator(Notation.getInstance().getNominator())
                                                         .setDenominator(Notation.getInstance().getDenominator()));
                        break;
                        case ViewConstants.INSERT_BAR_KEYCODE : sequenceView.addMusicalElement(
                                tick, new Beat().setMeasureBeat(true));
                        break;
                        case ViewConstants.REMOVE_LEFT_KEYCODE : sequenceView.removeMusicalElement((MusicalElement) staffElement);;
                        break;
                        case ViewConstants.MOVE_LEFT_KEYCODE : if(caret.isSet()) caretPrevious(e.isShiftDown());/*else if(selectedStaffSymbol != null) previousSelectedSymbol();*/ //selectPreviousPart();
                        break;
                        case ViewConstants.MOVE_RIGHT_KEYCODE : if(caret.isSet()) caretNext(e.isShiftDown()); /*else if(selectedStaffSymbol != null) nextSelectedSymbol();*/ //else selectNextPart();
                        break;
                        case ViewConstants.MOVE_UP_KEYCODE : if(e.isShiftDown()) symbolUp(getNoteSymbol()); else selectPartAbove();
                        break;
                        case ViewConstants.MOVE_DOWN_KEYCODE : if(e.isShiftDown()) symbolDown(getNoteSymbol()); else selectPartBelow();
                        break;
                        //default: System.out.println("none");
                }
                repaint();
        }

        public void keyReleased(KeyEvent e) {}

        public void keyTyped(KeyEvent e) {}

        public void keyPressed(KeyEvent e)
        {
                handleKeyEvent(e);
        }

	public void addObserver(Observer o)
	{
		if (!observers.contains(o))
			observers.add(o);
	}

	public boolean containsObserver(Observer o)
	{
		return observers.contains(o);
	}

	public void removeObserver(Observer o)
	{
		observers.remove(o);
	}

	public void notifyObservers()
	{
		for (Observer o: observers)
			o.onNotify(this);
	}

	public void notifyObserversExcept(Observer except)
	{
		for (Observer o: observers)
			if (o != except)
				o.onNotify(this);
	}

        public void onNotify(Object source)
        {
                //repaint();
        }

        private class RoundRelation extends ABRelation<Double, Double>
        {
                public RoundRelation(MutableValueNode<Double> a, MutableValueNode<Double> b)
                {
                        super(a, b);
                }

                @Override
                public void updateB()
                {
                        b.setValue(Math.ceil(a.getValue().doubleValue()));
                }

                @Override
                public void updateA()
                {
                        a.setValue(b.getValue().doubleValue());
                }
        }
}
